/* See COPYRIGHT for copyright information. */

#include <inc/x86.h>
#include <inc/mmu.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/assert.h>

#include <kern/pmap.h>
#include <kern/kclock.h>
#include <kern/env.h>
#include <kern/cpu.h>


// These variables are set by i386_detect_memory()
// 这些变量是由i386_detect_memory()设置的
size_t npages;            // Amount of physical memory (in pages) 物理内存量（以页为单位）
static size_t npages_basemem;    // Amount of base memory (in pages) 基本内存量（以页为单位）

// These variables are set in mem_init()
// 这些变量是由mem_init()设置的
pde_t *kern_pgdir;        // Kernel's initial page directory 内核的初始页面目录
struct PageInfo *pages;        // Physical page state array 物理页状态数组
static struct PageInfo *page_free_list;    // Free list of physical pages
// 物理空闲页面的列表

// --------------------------------------------------------------
// Detect machine's physical memory setup.
// 检测机器的物理内存设置。
// --------------------------------------------------------------

static int
nvram_read(int r) {
    return mc146818_read(r) | (mc146818_read(r + 1) << 8);
}

/*
 * 之前我们介绍过，jos把整个物理内存空间划分成三个部分：

　　一个是从0x00000~0xA0000，这部分也叫basemem，是可用的。

　　紧接着是0xA0000~0x100000，这部分叫做IO hole，是不可用的，主要被用来分配给外部设备了。

　　再紧接着就是0x100000~0x，这部分叫做extmem，是可用的，这是最重要的内存区域。

　　这个子函数中包括三个变量，其中npages记录整个内存的页数，npages_basemem记录basemem的页数，npages_extmem记录extmem的页数。
 */
static void
i386_detect_memory(void) {
    size_t basemem, extmem, ext16mem, totalmem;

    // Use CMOS calls to measure available base & extended memory.
    // (CMOS calls return results in kilobytes.)
    // 使用CMOS调用来测量可用的基本内存和扩展内存。
    //（CMOS调用返回的结果以KB为单位。）
    basemem = nvram_read(NVRAM_BASELO);
    extmem = nvram_read(NVRAM_EXTLO);
    ext16mem = nvram_read(NVRAM_EXT16LO) * 64;

    // Calculate the number of physical pages available in both base
    // and extended memory.
    // 计算基本内存和扩展内存中可用的物理页数。
    if (ext16mem)
        totalmem = 16 * 1024 + ext16mem;
    else if (extmem)
        totalmem = 1 * 1024 + extmem;
    else
        totalmem = basemem;

    npages = totalmem / (PGSIZE / 1024);
    npages_basemem = basemem / (PGSIZE / 1024);
    cprintf("Physical memory: %uK available, base = %uK, extended = %uK\n",
            totalmem, basemem, totalmem - basemem);
}


// --------------------------------------------------------------
// Set up memory mappings above UTOP. 在UTOP上设置内存映射。
// --------------------------------------------------------------

static void mem_init_mp(void);

static void boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm);

static void check_page_free_list(bool only_low_memory);

static void check_page_alloc(void);

static void check_kern_pgdir(void);

static physaddr_t check_va2pa(pde_t *pgdir, uintptr_t va);

static void check_page(void);

static void check_page_installed_pgdir(void);

// This simple physical memory allocator is used only while JOS is setting
// up its virtual memory system.  page_alloc() is the real allocator.
//
// If n>0, allocates enough pages of contiguous physical memory to hold 'n'
// bytes.  Doesn't initialize the memory.  Returns a kernel virtual address.
//
// If n==0, returns the address of the next free page without allocating
// anything.
//
// If we're out of memory, boot_alloc should panic.
// This function may ONLY be used during initialization,
// before the page_free_list list has been set up.
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// 这个简易的物理内存分配器只在JOS设置虚拟内存系统时使用。
// page_alloc()才是真正的内存分配器, 而boot_alloc只是对内存位置做标记。
//
// 1.如果n>0，则分配足够多的连续物理内存页以容纳'n'字节,不初始化内存。返回内核虚拟地址。
//
// 2.如果n==0，返回下一个空闲页的地址，不分配任何东西。
//
// 3.如果申请超出了内存，boot_alloc应该报错。
// 这个函数只能在初始化过程中使用。
// 在page_free_list列表建立之前。
//
// Note that when this function is called, we are still using entry_pgdir,
// which only maps the first 4MB of physical memory.
static void *
boot_alloc(uint32_t n) {
    static char *nextfree;    // 指向空闲内存的下一字节的虚拟地址 virtual address of next byte of free memory

    char *result;

    // Initialize nextfree if this is the first time.
    // 'end' is a magic symbol automatically generated by the linker,
    // which points to the end of the kernel's bss segment:
    // the first virtual address that the linker did *not* assign
    // to any kernel code or global variables.
    //
    // 如果nextfree为null时，则初始化 nextfree
    // 'end'是一个由链接器自动生成的神奇符号，指向内核bss段的末端: 链接器*没有*分配给任何内核代码或全局变量的第一个虚拟地址。

    if (!nextfree) { // 如果nextfree是null的话(!null = !0 = 1)
        extern char end[];
        nextfree = ROUNDUP((char *) end, PGSIZE); // 四舍五入到PGSIZE(页单位大小)的最近倍数,读取数据以页为单位,接近页大小更好一次性读取
    }

    // Allocate a chunk large enough to hold 'n' bytes, then update
    // nextfree.  Make sure nextfree is kept aligned
    // to a multiple of PGSIZE.
    // 分配一个足以容纳'n'字节的内存块，然后赋给nextfree, 确保nextfree与PGSIZE的倍数保持一致。
    //

    // LAB 2: Your code here.
    result = nextfree;
    nextfree = ROUNDUP(nextfree + n, PGSIZE);
    if (((uint32_t) nextfree - KERNBASE) > npages * PGSIZE) panic("Out of Memory!");
    // (char *)(nextfree + n) --> nextfree向后移动n个字节的内存地址
    return result;
}

// Set up a two-level page table:
//    kern_pgdir is its linear (virtual) address of the root
//
// This function only sets up the kernel part of the address space
// (ie. addresses >= UTOP).  The user part of the address space
// will be set up later.
//
// From UTOP to ULIM, the user is allowed to read but not write.
// Above ULIM the user cannot read or write.
//
// 设置一个两级页表：kern_pgdir是其线性（虚拟）地址的根
//
// 这个函数只设置了地址空间的内核部分(即地址>= UTOP)。 地址空间的用户部分在以后设置。
//
// 从 UTOP 到 ULIM，用户被允许读，但不能写。(UserTop ~ UserLimit)
// 超过ULIM，用户不能读或写。
void
mem_init(void) {
    uint32_t cr0;
    size_t n;

    // Find out how much memory the machine has (npages & npages_basemem).
    // 获取机器有多少内存（npages & npages_basemem）
    i386_detect_memory();

    // Remove this line when you're ready to test this function.
    // 当你准备测试这个函数时，删除这一行。
    // panic("mem_init: This function is not finished\n");

    //////////////////////////////////////////////////////////////////////
    // create initial page directory. 创建初始页面目录。
    kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
    memset(kern_pgdir, 0, PGSIZE);// 初始化, 将内核页目录中初始化为0

    //////////////////////////////////////////////////////////////////////
    // Recursively insert PD in itself as a page table, to form
    // a virtual page table at virtual address UVPT.
    // (For now, you don't have understand the greater purpose of the
    // following line.)

    // 递归地将页目录作为页表插入自身，以在虚拟地址UVPT处形成一个虚拟页表。
    // (目前，您没必要理解下面这句话的更深的意义。-- 仅需要虚拟地址就可以得到页目录起始地址)

    // Permissions: kernel R, user R , 权限：内核R，用户R
    kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;

    //////////////////////////////////////////////////////////////////////
    // Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
    // The kernel uses this array to keep track of physical pages: for
    // each physical page, there is a corresponding struct PageInfo in this
    // array.  'npages' is the number of physical pages in memory.  Use memset
    // to initialize all fields of each struct PageInfo to 0.
    //
    // 分配 npages 个的'struct PageInfo'结构的数组，并将其存储在'pages'中。
    /* 内核使用这个数组来跟踪物理页面：每个物理页面，在这个数组中都有一个相应的结构PageInfo。
       npages是内存中物理页的数量。 使用memset来初始化每个结构PageInfo的所有字段为0 */
    // Your code goes here:
    pages = (struct PageInfo *) boot_alloc(npages * sizeof(struct PageInfo));
    memset(pages, 0, npages * sizeof(struct PageInfo));
    //////////////////////////////////////////////////////////////////////
    // Make 'envs' point to an array of size 'NENV' of 'struct Env'.
    // LAB 3: Your code here.
    envs = (struct Env *) boot_alloc(NENV * sizeof(struct Env));
    memset(envs, 0, NENV * sizeof(struct Env));
    //////////////////////////////////////////////////////////////////////
    // Now that we've allocated the initial kernel data structures, we set
    // up the list of free physical pages. Once we've done so, all further
    // memory management will go through the page_* functions. In
    // particular, we can now map memory using boot_map_region
    // or page_insert
    /* 现在我们已经分配了初始的内核数据结构，我们设置了空闲物理页的列表。
        一旦我们这样做了，所有进一步的内存管理将通过page_*函数进行。
        特别是，我们现在可以使用 boot_map_region 或 page_insert 来映射内存。*/

    page_init();

    check_page_free_list(1);
    check_page_alloc();
    check_page();

    //////////////////////////////////////////////////////////////////////
    // Now we set up virtual memory 现在我们设置了虚拟内存
    //////////////////////////////////////////////////////////////////////
    // Map 'pages' read-only by the user at linear address UPAGES
    // Permissions:
    //    - the new image at UPAGES -- kernel R, user R (ie. perm = PTE_U | PTE_P)
    //    - pages itself 			-- kernel RW, user NONE
    // 在线性地址UPAGES处映射用户只读的 "pages"
    // 权限:
    // -在 UPAGES 处的新映像 -- 内核R，用户R(即 perm = PTE_U | PTE_P)
    // -页面本身 -内核RW，用户NONE
    // Your code goes here:
    boot_map_region(
            kern_pgdir,
            UPAGES,
            PTSIZE,
            PADDR(pages),
            PTE_U
    );
    //////////////////////////////////////////////////////////////////////
    // Map the 'envs' array read-only by the user at linear address UENVS
    // 在线性地址UENVS上映射 envs 数组为用户 只读权限
    // (ie. perm = PTE_U | PTE_P).
    // Permissions 要求:
    //    - the new image at UENVS  -- kernel R, user R
    //    - envs itself -- kernel RW, user NONE
    // LAB 3: Your code here.
    boot_map_region(
            kern_pgdir,
            UENVS,
            PTSIZE,
            PADDR(envs),
            PTE_U
    );

    //////////////////////////////////////////////////////////////////////
    // Use the physical memory that 'bootstack' refers to as the kernel
    // stack.  The kernel stack grows down from virtual address KSTACKTOP.
    // We consider the entire range from [KSTACKTOP-PTSIZE, KSTACKTOP)
    // to be the kernel stack, but break this into two pieces:
    //     * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
    //     * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed; so if
    //       the kernel overflows its stack ,it will fault rather than
    //       overwrite memory.  Known as a "guard page".
    //     Permissions: kernel RW, user NONE
    //
    // 使用'bootstack'所指的物理内存地址作为内核栈。
    // 内核堆栈从虚拟地址 KSTACKTOP 向下扩展。
    // 我们考虑[KSTACKTOP-PTSIZE，KSTACKTOP]的整个范围 4MB
    // 作为内核堆栈，但将其分为两部分：
    // 		* [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- 由物理内存支持的 32KB
    // 		* [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) --不被支持, 4MB - 32KB
    //		  所以如果内核溢出其堆栈，它将出现故障而不是覆盖溢出堆栈的内存空间.这样被称为 "保护页"。
    //
    // 		  权限：内核RW，用户NONE
    // Your code goes here:
    boot_map_region(
            kern_pgdir,
            KSTACKTOP - KSTKSIZE,
            KSTKSIZE,
            PADDR(bootstack),
            PTE_W
    );

    //////////////////////////////////////////////////////////////////////
    // Map all of physical memory at KERNBASE.
    // Ie.  the VA range [KERNBASE, 2^32) should map to
    //      the PA range [0, 2^32 - KERNBASE)
    // We might not have 2^32 - KERNBASE bytes of physical memory, but
    // we just set up the mapping anyway.
    // Permissions: kernel RW, user NONE
    //
    // 在KERNBASE处映射所有的物理内存.
    // 即VA范围[KERNBASE, 2^32)应该映射到PA范围[0, 2^32 - KERNBASE)
    // 我们可能没有2^32 - KERNBASE字节的物理内存，但是我们还是设置了这个映射。
    // 权限：内核RW，用户NONE
    // Your code goes here: boot_map_region 或 page_insert
    boot_map_region(
            kern_pgdir,
            KERNBASE,
            0xffffffff - KERNBASE,
            0,
            PTE_W
    );

    // Initialize the SMP-related parts of the memory map
    mem_init_mp();

    // Check that the initial page directory has been set up correctly.
    // 检查初始页面目录是否已经正确设置。
    check_kern_pgdir();

    // Switch from the minimal entry page directory to the full kern_pgdir
    // page table we just created.	Our instruction pointer should be
    // somewhere between KERNBASE and KERNBASE+4MB right now, which is
    // mapped the same way by both page tables.
    //
    // 从最小的入口页面目录切换到我们刚刚创建的完整的kern_pgdir的页表。
    // 我们的指令指针现在应该在KERNBASE和KERNBASE+4MB之间,这两个页表的映射方式是一样的。
    //
    // If the machine reboots at this point, you've probably set up your
    // kern_pgdir wrong.
    // 如果机器在这时重新启动，你可能把你的kern_pgdir设置错了。
    lcr3(PADDR(kern_pgdir));

    check_page_free_list(0);

    // entry.S set the really important flags in cr0 (including enabling
    // paging).  Here we configure the rest of the flags that we care about.
    // entry.S设置cr0中真正重要的标志（包括启用paging）
    // 这里我们配置其余有意义的标志。
    cr0 = rcr0();
    cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_MP;
    cr0 &= ~(CR0_TS | CR0_EM);
    lcr0(cr0);

    // Some more checks, only possible after kern_pgdir is installed.
    // 还有一些检查，只有在安装了kern_pgdir之后才能进行。
    check_page_installed_pgdir();
}

// Modify mappings in kern_pgdir to support SMP
//   - Map the per-CPU stacks in the region [KSTACKTOP-PTSIZE, KSTACKTOP)
//
static void
mem_init_mp(void) {
    // Map per-CPU stacks starting at KSTACKTOP, for up to 'NCPU' CPUs.
    //
    // For CPU i, use the physical memory that 'percpu_kstacks[i]' refers
    // to as its kernel stack. CPU i's kernel stack grows down from virtual
    // address kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP), and is
    // divided into two pieces, just like the single stack you set up in
    // mem_init:
    //     * [kstacktop_i - KSTKSIZE, kstacktop_i)
    //          -- backed by physical memory
    //     * [kstacktop_i - (KSTKSIZE + KSTKGAP), kstacktop_i - KSTKSIZE)
    //          -- not backed; so if the kernel overflows its stack,
    //             it will fault rather than overwrite another CPU's stack.
    //             Known as a "guard page".
    //     Permissions: kernel RW, user NONE
    //
    // LAB 4: Your code here:
    for (int i = 0; i < NCPU; i++) {
        uintptr_t kstacktop_i = (KSTACKTOP - i * (KSTKSIZE + KSTKGAP));

        boot_map_region(kern_pgdir,
                        kstacktop_i - KSTKSIZE,
                // 为什么要减KSTKSIZE?
                // -- 因为函数映射是从va向上增长的，而kstacktop_i是栈底，栈底是不能逾越的且栈顶是向下增长的。
                // 所以,减去的话,kstacktop_i - KSTKSIZE表示的是栈顶，不减去的话 kstacktop_i就是栈顶
                        KSTKSIZE,
                        PADDR(percpu_kstacks[i]),
                        PTE_W);
    }
}

// --------------------------------------------------------------
// Tracking of physical pages.
// The 'pages' array has one 'struct PageInfo' entry per physical page.
// Pages are reference counted, and free pages are kept on a linked list.
// --------------------------------------------------------------

//
// Initialize page structure and memory free list.
// After this is done, NEVER use boot_alloc again.  ONLY use the page
// allocator functions below to allocate and deallocate physical
// memory via the page_free_list.
//
// --------------------------------------------------------------
// 追踪物理页面。
// 'pages'数组中每个物理页有一个'struct PageInfo'条目。
// 页面被引用计数，空闲页面被保存在一个链接列表中。
// --------------------------------------------------------------

//
// 初始化页面结构和内存空闲列表。
// 在这之后，永远不会再使用 boot_alloc。 只使用下面的页面分配器函数来分配和删除物理内存。
// 通过page_free_list分配和删除物理内存。
//
void
page_init(void) {
    // LAB 4:
    // Change your code to mark the physical page at MPENTRY_PADDR as in use

    // The example code here marks all physical pages as free.
    // However this is not truly the case.  What memory is free?
    //  1) Mark physical page 0 as in use.
    //     This way we preserve the real-mode IDT and BIOS structures
    //     in case we ever need them.  (Currently we don't, but...)
    //  2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
    //     is free.
    //  3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
    //     never be allocated.
    //  4) Then extended memory [EXTPHYSMEM, ...).
    //     Some of it is in use, some is free. Where is the kernel
    //     in physical memory?  Which pages are already in use for
    //     page tables and other data structures?
    //
    // Change the code to reflect this.
    // NB: DO NOT actually touch the physical memory corresponding to
    // free pages!
    //
    // 这里的示例代码将所有的物理页面标记为空闲。
    // 然而这并不是真正的情况。
    //
    // 哪些内存是空闲的？
    // 1）物理页第0页为已使用
    // 	 这样我们就可以保留实模式下的 IDT 和 BIOS 结构。
    // 	 以防万一我们需要它们。 (目前我们不需要，但是...)
    //
    // 2) 剩下的基本内存，[PGSIZE, npages_basemem * PGSIZE)是空闲的。
    //
    // 3) 然后是IO洞[IOPHYSMEM, EXTPHYSMEM)，它肯定是无法被分配的。
    // {
    // 		#define IOPHYSMEM	0x0A0000
    // 		#define EXTPHYSMEM	0x100000
    //
    //	}
    //
    // 4) 然后是扩展内存 [EXTPHYSMEM, ...)。
    //	有些在使用中，有些是空闲的。内核在哪里
    // 	在物理内存中？ 哪些页已经在使用，用于
    // 	页表和其他数据结构？
    //
    // 修改代码来表示上述的意思。
    // 注意：不要真正的去接触空闲页对应的物理内存!

    uint32_t alloced_pn = ((uint32_t) boot_alloc(0) - KERNBASE) / PGSIZE;
    int num_iohole = 96; // (0x100000 - 0x0A0000)/4096 = 96个页
    uint32_t mpentry_pn = ((uint32_t) KADDR(MPENTRY_PADDR) - KERNBASE) / PGSIZE;
    int i;
    for (i = 0; i < npages; i++) {
        if (i == 0 || i == mpentry_pn) {
            pages[i].pp_ref = 1;
            pages[i].pp_link = NULL;
        } else if (i >= npages_basemem && i < npages_basemem + num_iohole + alloced_pn) {
            pages[i].pp_ref = 1;
            pages[i].pp_link = NULL;
        } else {
            pages[i].pp_ref = 0;
            pages[i].pp_link = page_free_list;
            page_free_list = &pages[i]; // 制造链式结构
        }

    }
}

//
// Allocates a physical page.  If (alloc_flags & ALLOC_ZERO), fills the entire
// returned physical page with '\0' bytes.  Does NOT increment the reference
// count of the page - the caller must do these if necessary (either explicitly
// or via page_insert).
// 分配一个物理页。 如果（alloc_flags & ALLOC_ZERO），用'\0'字节填充整个返回的物理页。
// 不增加页面的引用计数--如果需要，调用者必须做这些事情（明确地或通过page_insert）。
//
// Be sure to set the pp_link field of the allocated page to NULL so
// page_free can check for double-free bugs.
// 请确保将分配的页面的pp_link字段设置为NULL，以便
// page_free可以检查是否有double-free漏洞。
//
// Returns NULL if out of free memory. 如果没有可用的内存，则返回NULL。
//
// Hint: use page2kva and memset 提示：使用 page2kva 和 memset。
struct PageInfo *
page_alloc(int alloc_flags) {
    // Fill this function in
    struct PageInfo *p = page_free_list;
    if (p == NULL) {
        return NULL;
    }
    page_free_list = page_free_list->pp_link;
    p->pp_link = NULL;
    if (alloc_flags & ALLOC_ZERO) {
        memset(page2kva(p), 0, PGSIZE);
    }

    return p;
}

//
// Return a page to the free list.
// (This function should only be called when pp->pp_ref reaches 0.)
//
//
// 将返回一个页面给空闲列表。
//（只有当pp->pp_ref达到0时，才应调用此函数。）
//
void
page_free(struct PageInfo *pp) {
    // Fill this function in 填写此函数
    assert(pp->pp_ref == 0);
    assert(pp->pp_link == NULL);
    pp->pp_link = page_free_list;
    page_free_list = pp;
    // Hint: You may want to panic if pp->pp_ref is nonzero or
    // pp->pp_link is not NULL.
    // 提示：如果pp->pp_ref为非零或者pp->pp_link不是NULL，你可能会感到恐慌。

}

//
// Decrement the reference count on a page,
// freeing it if there are no more refs.
//
// 减少一个页面上的引用计数。
// 如果没有更多的引用，就释放它。
//
//
void
page_decref(struct PageInfo *pp) {
    if (--pp->pp_ref == 0)
        page_free(pp);
}

// Given 'pgdir', a pointer to a page directory, pgdir_walk returns
// a pointer to the page table entry (PTE) for linear address 'va'.
// This requires walking the two-level page table structure.
//
// The relevant page table page might not exist yet.
// If this is true, and create == false, then pgdir_walk returns NULL.
// Otherwise, pgdir_walk allocates a new page table page with page_alloc.
//    - If the allocation fails, pgdir_walk returns NULL.
//    - Otherwise, the new page's reference count is incremented,
//	the page is cleared,
//	and pgdir_walk returns a pointer into the new page table page.
//
// Hint 1: you can turn a PageInfo * into the physical address of the
// page it refers to with page2pa() from kern/pmap.h.
//
// Hint 2: the x86 MMU checks permission bits in both the page directory
// and the page table, so it's safe to leave permissions in the page
// directory more permissive than strictly necessary.
//
// 给出'pgdir'(一个指向页目录的指针)，pgdir_walk返回一个指向线性地址'va'的页表项（PTE）的指针。
// 这需要走两层页表结构。
//
// 相关的页表项可能还不存在。
// 如果这是真的，并且create == false，那么pgdir_walk返回NULL。
// 否则，pgdir_walk会用page_alloc分配一个新的页表页。
// - 如果分配失败，pgdir_walk返回NULL。
// - 否则，新页的引用计数被递增,该页被清除，然后pgdir_walk返回一个页表项的指针。
//
// 提示1：你可以用kern/pmap.h中的page2pa()将PageInfo *变成它所引用页面的物理地址。
//
// 提示2：X86的MMU检查页目录和页表的权限位，
// 所以把页目录中的权限留得比严格意义上的权限更多是安全的。
//
// Hint 3: look at inc/mmu.h for useful macros that manipulate page
// table and page directory entries.
// 提示3：看看inc/mmu.h中的有用的宏，可以操作页表和页目录条目。
//
pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create) {
    // Fill this function in
    struct PageInfo *new_p = NULL;
    pte_t *pgtb_base = NULL;
    unsigned pgdir_off, pgtb_off;
    pgdir_off = PDX(va);
    pgtb_off = PTX(va);
    pde_t *pgtb = pgdir + pgdir_off;

    if (!(*pgtb & PTE_P)) { // PTE_P 代表 页目录/页表 是否存在
        if (!create) return NULL;
        if (!(new_p = page_alloc(1))) return NULL;

        new_p->pp_ref++; // 此页被引用次数+1
        *pgtb = (page2pa(new_p) | PTE_P | PTE_W | PTE_U);
    }
    pgtb_base = KADDR(PTE_ADDR(*pgtb)); // 页表基地址 KADDR将物理地址转换为内核虚拟地址
    // PTE_ADDR返回页表项的基地址；

    return &pgtb_base[pgtb_off]; // &pgtb_base[pgtb_off] = pgtb_base + pgtb_off
}

//
// Map [va, va+size) of virtual address space to physical [pa, pa+size)
// in the page table rooted at pgdir.  Size is a multiple of PGSIZE, and
// va and pa are both page-aligned.
// Use permission bits perm|PTE_P for the entries.
//
// This function is only intended to set up the ``static'' mappings
// above UTOP. As such, it should *not* change the pp_ref field on the
// mapped pages.
//
//
//将虚拟地址空间的[va，va+size）映射到以pgdir为根的页表中的物理[pa，pa+size）。
//size是PGSIZE的倍数，va和pa都是页对齐的。
//对条目使用权限位perm | PTE_P。
//
//此函数仅用于在UTOP上设置“静态”映射。因此，它不应*更改映射页面上的pp_ref字段。
//
//提示：TA解决方案使用pgdir_walk
// Hint: the TA solution uses pgdir_walk
/*   int array[5] = {0};
	--	array   = &array   = &array[0]
    --	array+1 = &array[0]+1
    --  &array+1 (移动 sizeof(array)个单位)
*/
static void
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm) {
    // Fill this function in
    int nadd;
    pte_t *pte = NULL;
    for (nadd = 0; nadd < size; nadd += PGSIZE) {
        pte = pgdir_walk(pgdir, (void *) va, 1);    //Get the table entry of this page.
        *pte = (pa | perm | PTE_P);
        pa += PGSIZE;
        va += PGSIZE;
    }
}

//
// Map the physical page 'pp' at virtual address 'va'.
// The permissions (the low 12 bits) of the page table entry
// should be set to 'perm|PTE_P'.
//
// Requirements
//   - If there is already a page mapped at 'va', it should be page_remove()d.
//   - If necessary, on demand, a page table should be allocated and inserted
//     into 'pgdir'.
//   - pp->pp_ref should be incremented if the insertion succeeds.
//   - The TLB must be invalidated if a page was formerly present at 'va'.
//
// Corner-case hint: Make sure to consider what happens when the same
// pp is re-inserted at the same virtual address in the same pgdir.
// However, try not to distinguish this case in your code, as this
// frequently leads to subtle bugs; there's an elegant way to handle
// everything in one code path.
//
// RETURNS:
//   0 on success
//   -E_NO_MEM, if page table couldn't be allocated
//
// Hint: The TA solution is implemented using pgdir_walk, page_remove,
// and page2pa.
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// 在虚拟地址'va'上映射物理页'pp'。
// PTE的权限（低12位）应该设置为'perm|PTE_P'。
//
// 要求
// - 如果在'va'处已经有一个页面被映射，它应该被page_remove()。
// - 如果有必要，在需要的时候，应该分配一个页表并将其插入到'pgdir'中。
// - 如果插入成功，pp->pp_ref应该被递增。
// - 如果一个页面以前存在于'va'中，TLB 必须被废止。
//
// 角落案例提示： 确保考虑在同一个pgdir中的同一个虚拟地址重新插入同一个pp时会发生什么。
// 				然而，尽量不要在代码中区分这种情况，因为这经常会导致难以发现的错误；
// 				有一种优雅的方法可以在一个代码路径中处理所有事情。
//
// 返回:
// 			0 表示成功。
// 			-E_NO_MEM，如果页表不能被分配。
//
// 提示：TA的解决方案是用pgdir_walk, page_remove 和page2pa。
//
//
int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm) {
    // Fill this function in
    pte_t *pte;
    if (!(pte = pgdir_walk(pgdir, va, 1))) { //分配给pgdir
        return -E_NO_MEM; // 内存不足 无法分配
    }
    if ((*pte) & PTE_P) {
        if (PTE_ADDR(*pte) == page2pa(pp)) {
            (*pp).pp_ref--;
        } else {
            page_remove(pgdir, va);
        }
    }
    pp->pp_ref++;
    *pte = (page2pa(pp) | perm | PTE_P);
    return 0;
}

//
// Return the page mapped at virtual address 'va'.
// If pte_store is not zero, then we store in it the address
// of the pte for this page.  This is used by page_remove and
// can be used to verify page permissions for syscall arguments,
// but should not be used by most callers.
//
// Return NULL if there is no page mapped at va.
// Hint: the TA solution uses pgdir_walk and pa2page.
//
// 返回在虚拟地址'va'处映射的页面项(4KB)。
// 如果pte_store不是null，那么我们将该页面的pte地址存储在其中。
// 这将被page_remove使用，并可用于验证syscall参数的页面权限。但大多数调用者不应该使用。
//
// 如果在va处没有映射的页面，则返回NULL。
//
// 提示：TA的解决方案使用 pgdir_walk 和 pa2page。
//

struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store) {
    // Fill this function in
    pte_t *pte = NULL;
    if (!(pte = pgdir_walk(pgdir, va, 0))) {
        return NULL;
    }
    if (!(*pte & PTE_P)) {
        return NULL;
    }
    if (pte_store != NULL) {
        *pte_store = pte;
    }


    return pa2page(PTE_ADDR(*pte));
}

//
// Unmaps the physical page at virtual address 'va'.
// If there is no physical page at that address, silently does nothing.
//
// Details:
//   - The ref count on the physical page should decrement.
//   - The physical page should be freed if the refcount reaches 0.
//   - The pg table entry corresponding to 'va' should be set to 0.
//     (if such a PTE exists)
//   - The TLB must be invalidated if you remove an entry from
//     the page table.
//
// Hint: The TA solution is implemented using page_lookup,
// tlb_invalidate, and page_decref.
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// 解除虚拟地址'va'处的物理页的映射。
// 如果在这个地址上没有物理页，就不做任何事情。
//
// 详细说明：
// 		- 物理页上的ref计数应该-1。
// 		- 如果 ref 达到 0，物理页应该被释放。
// 		- 对应于'va'的 PTE 应该被设置为0。(如果存在这样的PTE)
// 		- 如果你从页表中删除了一个条目，TLB 必须被废止。
//
// 提示：TA的解决方案是用 page_lookup 和 tlb_invalidate 和 page_decref实现的
//
void
page_remove(pde_t *pgdir, void *va) {
    // Fill this function in
    // 记录一波 第一次自己手写后检查全对,
    pde_t *pte = NULL;
    struct PageInfo *rpte;
    if (!(rpte = page_lookup(pgdir, va, &pte))) {
        return;
    }
    *pte = 0;
    page_decref(rpte);
    tlb_invalidate(pgdir, va);
}

//
// Invalidate a TLB entry, but only if the page tables being
// edited are the ones currently in use by the processor.
// 使TLB条目失效，但只有在 被编辑的页表 是 处理器当前使用的页表时 才会失效。
//
void
tlb_invalidate(pde_t *pgdir, void *va) {
    // Flush the entry only if we're modifying the current address space.
    if (!curenv || curenv->env_pgdir == pgdir)
        invlpg(va);
}

//
// Reserve size bytes in the MMIO region and map [pa,pa+size) at this
// location.  Return the base of the reserved region.  size does *not*
// have to be multiple of PGSIZE.
//
void *
mmio_map_region(physaddr_t pa, size_t size) {
    // Where to start the next region.  Initially, this is the
    // beginning of the MMIO region.  Because this is static, its
    // value will be preserved between calls to mmio_map_region
    // (just like nextfree in boot_alloc).
    static uintptr_t base = MMIOBASE; // C语言的静态变量只初始化一次，也就是说这条语句只执行一次
    // Reserve size bytes of virtual memory starting at base and
    // map physical pages [pa,pa+size) to virtual addresses
    // [base,base+size).  Since this is device memory and not
    // regular DRAM, you'll have to tell the CPU that it isn't
    // safe to cache access to this memory.  Luckily, the page
    // tables provide bits for this purpose; simply create the
    // mapping with PTE_PCD|PTE_PWT (cache-disable and write-through) in addition to PTE_W.  (If you're interested
    // in more details on this, see section 10.5 of IA32 volume
    // 3A.)
    //
    // Be sure to round size up to a multiple of PGSIZE and to
    // handle if this reservation would overflow MMIOLIM (it's
    // okay to simply panic if this happens).
    //
    // Hint: The staff solution uses boot_map_region.
    //
    // Your code here:
    pa = ROUNDDOWN(pa, PGSIZE);// 页面对齐
    size = ROUNDUP(size, PGSIZE); // 页面对齐

    if (size >= MMIOLIM) {
        panic("mmio_map_region error: 'size >= MMIOLIM' ");
    }
    boot_map_region(kern_pgdir, base, size, pa, PTE_PCD | PTE_PWT | PTE_W);
    uintptr_t res = base;
    base = base + size;// 分配了base后size，自然要将base后移动，因为只有[base+size，xx)没被使用
    return (void *) res;
}

static uintptr_t user_mem_check_addr;

//
// Check that an environment is allowed to access the range of memory
// [va, va+len) with permissions 'perm | PTE_P'.
// Normally 'perm' will contain PTE_U at least, but this is not required.
// 'va' and 'len' need not be page-aligned; you must test every page that
// contains any of that range.  You will test either 'len/PGSIZE',
// 'len/PGSIZE + 1', or 'len/PGSIZE + 2' pages.
//
// 检查权限为‘perm | PTE_P’的环境env在[va, va+len)是否有权限访问。
// 通常 prem 至少都会包含 PTE_U. 之后检查每一个页面
//
// A user program can access a virtual address if (1) the address is below
// ULIM, and (2) the page table gives it permission.  These are exactly
// the tests you should implement here.检查地址是否在ULIM之下，并且权限是否合格
//
// If there is an error, set the 'user_mem_check_addr' variable to the first
// erroneous virtual address.
//
// Returns 0 if the user program can access this range of addresses,
// and -E_FAULT otherwise.
//
int
user_mem_check(struct Env *env, const void *va, size_t len, int perm) {
    // LAB 3: Your code here.
    size_t start = (size_t) ROUNDDOWN(va, PGSIZE);
    size_t end = (size_t) ROUNDUP(va + len, PGSIZE);
    perm |= PTE_P;
    for (; start < end; start += PGSIZE) {
        pte_t *pte = pgdir_walk(env->env_pgdir, (void *) start, 0);
        if (start >= ULIM || pte == NULL || (*pte & PTE_P) != PTE_P || (*pte & perm) != perm) {
            user_mem_check_addr = (size_t) start < (size_t) va ? (size_t) va : start;
            return -E_FAULT;
        }
    }
    return 0;
}

//
// Checks that environment 'env' is allowed to access the range
// of memory [va, va+len) with permissions 'perm | PTE_U | PTE_P'.
// If it can, then the function simply returns.
// If it cannot, 'env' is destroyed and, if env is the current
// environment, this function will not return.
//
void
user_mem_assert(struct Env *env, const void *va, size_t len, int perm) {
    if (user_mem_check(env, va, len, perm | PTE_U) < 0) {
        cprintf("[%08x] user_mem_check assertion failure for "
                "va %08x\n", env->env_id, user_mem_check_addr);
        env_destroy(env);    // may not return
    }
}



// --------------------------------------------------------------
// Checking functions.
// --------------------------------------------------------------

//
// Check that the pages on the page_free_list are reasonable.
// 检查page_free_列表上的页面是否合理。
//
static void
check_page_free_list(bool only_low_memory) {
    struct PageInfo *pp;
    unsigned pdx_limit = only_low_memory ? 1 : NPDENTRIES;
    int nfree_basemem = 0, nfree_extmem = 0;
    char *first_free_page;

    if (!page_free_list)
        panic("'page_free_list' is a null pointer!");

    if (only_low_memory) {
        // Move pages with lower addresses first in the free
        // list, since entry_pgdir does not map all pages.
        // 在空闲列表中首先移动地址较低的页面，因为entry_pgdir没有映射所有页面。
        struct PageInfo *pp1, *pp2;
        struct PageInfo **tp[2] = {&pp1, &pp2};
        for (pp = page_free_list; pp; pp = pp->pp_link) {
            int pagetype = PDX(page2pa(pp)) >= pdx_limit;
            *tp[pagetype] = pp;
            tp[pagetype] = &pp->pp_link;
        }
        *tp[1] = 0;
        *tp[0] = pp2;
        page_free_list = pp1;
    }

    // if there's a page that shouldn't be on the free list,
    // try to make sure it eventually causes trouble.
    // 如果一个页面不应该出现在空闲列表中，请处理好这个页面的情况
    for (pp = page_free_list; pp; pp = pp->pp_link)
        if (PDX(page2pa(pp)) < pdx_limit)
            memset(page2kva(pp), 0x97, 128);

    first_free_page = (char *) boot_alloc(0);
    for (pp = page_free_list; pp; pp = pp->pp_link) {
        // check that we didn't corrupt the free list itself
        // 检查我们是否破坏了自由列表本身
        assert(pp >= pages);
        assert(pp < pages + npages);
        assert(((char *) pp - (char *) pages) % sizeof(*pp) == 0);

        // check a few pages that shouldn't be on the free list
        // 检查一些不应该出现在空闲列表上的页面
        assert(page2pa(pp) != 0);
        assert(page2pa(pp) != IOPHYSMEM);
        assert(page2pa(pp) != EXTPHYSMEM - PGSIZE);
        assert(page2pa(pp) != EXTPHYSMEM);
        assert(page2pa(pp) < EXTPHYSMEM || (char *) page2kva(pp) >= first_free_page);
        // (new test for lab 4)
        assert(page2pa(pp) != MPENTRY_PADDR);

        if (page2pa(pp) < EXTPHYSMEM)
            ++nfree_basemem;
        else
            ++nfree_extmem;
    }

    assert(nfree_basemem > 0);
    assert(nfree_extmem > 0);

    cprintf("check_page_free_list() Succeeded!\n");
}

//
// Check the physical page allocator (page_alloc(), page_free(),and page_init()).
// 检查物理页面分配器（page_alloc(), page_free(),and page_init()）
//
static void
check_page_alloc(void) {
    struct PageInfo *pp, *pp0, *pp1, *pp2;
    int nfree;
    struct PageInfo *fl;
    char *c;
    int i;

    if (!pages)
        panic("'pages' is a null pointer!");

    // check number of free pages
    // 统计空闲页面的数量
    for (pp = page_free_list, nfree = 0; pp; pp = pp->pp_link)
        ++nfree;

    // should be able to allocate three pages
    // 应该能够分配到三个页面

    pp0 = pp1 = pp2 = 0;
    assert((pp0 = page_alloc(0)));
    assert((pp1 = page_alloc(0)));
    assert((pp2 = page_alloc(0)));

    assert(pp0);
    assert(pp1 && pp1 != pp0);
    assert(pp2 && pp2 != pp1 && pp2 != pp0);
    assert(page2pa(pp0) < npages * PGSIZE);
    assert(page2pa(pp1) < npages * PGSIZE);
    assert(page2pa(pp2) < npages * PGSIZE);

    // temporarily steal the rest of the free pages
    // 暂时窃取剩余的空闲页面
    fl = page_free_list;
    page_free_list = 0;

    // should be no free memory
    // 应该没有空闲的内存
    assert(!page_alloc(0));

    // free and re-allocate?
    // 释放并重新分配？
    page_free(pp0);
    page_free(pp1);
    page_free(pp2);
    pp0 = pp1 = pp2 = 0;
    assert((pp0 = page_alloc(0)));
    assert((pp1 = page_alloc(0)));
    assert((pp2 = page_alloc(0)));
    assert(pp0);
    assert(pp1 && pp1 != pp0);
    assert(pp2 && pp2 != pp1 && pp2 != pp0);
    assert(!page_alloc(0));

    // test flagsALLOC_ZERO
    // 测试标志ALLOC_ZERO
    memset(page2kva(pp0), 1, PGSIZE);
    page_free(pp0);
    assert((pp = page_alloc(ALLOC_ZERO)));
    assert(pp && pp0 == pp);
    c = page2kva(pp);
    for (i = 0; i < PGSIZE; i++)
        assert(c[i] == 0);

    // give free list back
    // 归还空闲列表给page_free_list
    page_free_list = fl;

    // free the pages we took
    // 释放获取的页面
    page_free(pp0);
    page_free(pp1);
    page_free(pp2);

    // number of free pages should be the same
    // 空闲页面的数量应与之前统计的相同
    for (pp = page_free_list; pp; pp = pp->pp_link)
        --nfree;
    assert(nfree == 0);

    cprintf("check_page_alloc() Succeeded!\n");
}

//
// Checks that the kernel part of virtual address space
// has been set up roughly correctly (by mem_init()).
//
// This function doesn't test every corner case,
// but it is a pretty good sanity check.
//
// 检查虚拟地址空间的内核部分是否大致正确设置（由 mem_init() 设置）。
//
// 这个函数并不是测试每个细节，但它是一个相当好的健全检查。
//
//

static void
check_kern_pgdir(void) {
    uint32_t i, n;
    pde_t *pgdir;

    pgdir = kern_pgdir;

    // check pages array
    n = ROUNDUP(npages * sizeof(struct PageInfo), PGSIZE);
    for (i = 0; i < n; i += PGSIZE)
        assert(check_va2pa(pgdir, UPAGES + i) == PADDR(pages) + i);

    // check envs array (new test for lab 3)
    n = ROUNDUP(NENV * sizeof(struct Env), PGSIZE);
    for (i = 0; i < n; i += PGSIZE)
        assert(check_va2pa(pgdir, UENVS + i) == PADDR(envs) + i);


    // check phys mem
    for (i = 0; i < npages * PGSIZE; i += PGSIZE)
        assert(check_va2pa(pgdir, KERNBASE + i) == i);

    // check kernel stack
    // (updated in lab 4 to check per-CPU kernel stacks)
    for (n = 0; n < NCPU; n++) {
        uint32_t base = KSTACKTOP - (KSTKSIZE + KSTKGAP) * (n + 1);
        for (i = 0; i < KSTKSIZE; i += PGSIZE)
            assert(check_va2pa(pgdir, base + KSTKGAP + i)
                   == PADDR(percpu_kstacks[n]) + i);
        for (i = 0; i < KSTKGAP; i += PGSIZE)
            assert(check_va2pa(pgdir, base + i) == ~0);
    }

    // check PDE permissions
    for (i = 0; i < NPDENTRIES; i++) {
        switch (i) {
            case PDX(UVPT):
            case PDX(KSTACKTOP - 1):
            case PDX(UPAGES):
            case PDX(UENVS):
            case PDX(MMIOBASE):
                assert(pgdir[i] & PTE_P);
                break;
            default:
                if (i >= PDX(KERNBASE)) {
                    assert(pgdir[i] & PTE_P);
                    assert(pgdir[i] & PTE_W);
                } else
                    assert(pgdir[i] == 0);
                break;
        }
    }
    cprintf("check_kern_pgdir() Succeeded!\n");
}

// This function returns the physical address of the page containing 'va',
// defined by the page directory 'pgdir'.  The hardware normally performs
// this functionality for us!  We define our own version to help check
// the check_kern_pgdir() function; it shouldn't be used elsewhere.
// 该函数返回包含'va'的页面的物理地址,由页面目录'pgdir'定义。 硬件通常会执行这个功能！
// 这个功能是为我们准备的  我们定义我们自己的版本来帮助检查
// check_kern_pgdir()函数；它不应该被用在其他地方。
static physaddr_t
check_va2pa(pde_t *pgdir, uintptr_t va) {
    pte_t *p;

    pgdir = &pgdir[PDX(va)];
    if (!(*pgdir & PTE_P))
        return ~0;
    p = (pte_t *) KADDR(PTE_ADDR(*pgdir));
    if (!(p[PTX(va)] & PTE_P))
        return ~0;
    return PTE_ADDR(p[PTX(va)]);
}


// check page_insert, page_remove, &c
// 校验 page_insert, page_remove, &c
static void
check_page(void) {
    struct PageInfo *pp, *pp0, *pp1, *pp2;
    struct PageInfo *fl;
    pte_t *ptep, *ptep1;
    void *va;
    uintptr_t mm1, mm2;
    int i;
    extern pde_t entry_pgdir[];

    // should be able to allocate three pages
    pp0 = pp1 = pp2 = 0;
    assert((pp0 = page_alloc(0)));
    assert((pp1 = page_alloc(0)));
    assert((pp2 = page_alloc(0)));

    assert(pp0);
    assert(pp1 && pp1 != pp0);
    assert(pp2 && pp2 != pp1 && pp2 != pp0);

    // temporarily steal the rest of the free pages
    fl = page_free_list;
    page_free_list = 0;

    // should be no free memory
    assert(!page_alloc(0));

    // there is no page allocated at address 0
    assert(page_lookup(kern_pgdir, (void *) 0x0, &ptep) == NULL);

    // there is no free memory, so we can't allocate a page table
    // 没有可用内存，因此无法分配页表
    assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) < 0);

    // free pp0 and try again: pp0 should be used for page table
    // 释放pp0并重试：pp0应用于页表
    page_free(pp0);
    assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) == 0);
    assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0));
    assert(check_va2pa(kern_pgdir, 0x0) == page2pa(pp1));
    assert(pp1->pp_ref == 1);
    assert(pp0->pp_ref == 1);

    // should be able to map pp2 at PGSIZE because pp0 is already allocated for page table
    // 应该能够将pp2映射为PGSIZE，因为pp0已分配给页表
    assert(page_insert(kern_pgdir, pp2, (void *) PGSIZE, PTE_W) == 0);
    assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2));
    assert(pp2->pp_ref == 1);

    // should be no free memory
    assert(!page_alloc(0));

    // should be able to map pp2 at PGSIZE because it's already there
    assert(page_insert(kern_pgdir, pp2, (void *) PGSIZE, PTE_W) == 0);
    assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2));
    assert(pp2->pp_ref == 1);

    // pp2 should NOT be on the free list
    // could happen in ref counts are handled sloppily in page_insert
    assert(!page_alloc(0));

    // check that pgdir_walk returns a pointer to the pte
    ptep = (pte_t *) KADDR(PTE_ADDR(kern_pgdir[PDX(PGSIZE)]));
    assert(pgdir_walk(kern_pgdir, (void *) PGSIZE, 0) == ptep + PTX(PGSIZE));

    // should be able to change permissions too.
    assert(page_insert(kern_pgdir, pp2, (void *) PGSIZE, PTE_W | PTE_U) == 0);
    assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2));
    assert(pp2->pp_ref == 1);
    assert(*pgdir_walk(kern_pgdir, (void *) PGSIZE, 0) & PTE_U);
    assert(kern_pgdir[0] & PTE_U);

    // should be able to remap with fewer permissions
    assert(page_insert(kern_pgdir, pp2, (void *) PGSIZE, PTE_W) == 0);
    assert(*pgdir_walk(kern_pgdir, (void *) PGSIZE, 0) & PTE_W);
    assert(!(*pgdir_walk(kern_pgdir, (void *) PGSIZE, 0) & PTE_U));

    // should not be able to map at PTSIZE because need free page for page table
    assert(page_insert(kern_pgdir, pp0, (void *) PTSIZE, PTE_W) < 0);

    // insert pp1 at PGSIZE (replacing pp2)
    assert(page_insert(kern_pgdir, pp1, (void *) PGSIZE, PTE_W) == 0);
    assert(!(*pgdir_walk(kern_pgdir, (void *) PGSIZE, 0) & PTE_U));

    // should have pp1 at both 0 and PGSIZE, pp2 nowhere, ...
    assert(check_va2pa(kern_pgdir, 0) == page2pa(pp1));
    assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp1));
    // ... and ref counts should reflect this
    assert(pp1->pp_ref == 2);
    assert(pp2->pp_ref == 0);

    // pp2 should be returned by page_alloc
    assert((pp = page_alloc(0)) && pp == pp2);

    // unmapping pp1 at 0 should keep pp1 at PGSIZE
    page_remove(kern_pgdir, 0x0);
    assert(check_va2pa(kern_pgdir, 0x0) == ~0);
    assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp1));
    assert(pp1->pp_ref == 1);
    assert(pp2->pp_ref == 0);

    // test re-inserting pp1 at PGSIZE
    assert(page_insert(kern_pgdir, pp1, (void *) PGSIZE, 0) == 0);
    assert(pp1->pp_ref);
    assert(pp1->pp_link == NULL);

    // unmapping pp1 at PGSIZE should free it
    page_remove(kern_pgdir, (void *) PGSIZE);
    assert(check_va2pa(kern_pgdir, 0x0) == ~0);
    assert(check_va2pa(kern_pgdir, PGSIZE) == ~0);
    assert(pp1->pp_ref == 0);
    assert(pp2->pp_ref == 0);

    // so it should be returned by page_alloc
    assert((pp = page_alloc(0)) && pp == pp1);

    // should be no free memory
    assert(!page_alloc(0));

    // forcibly take pp0 back
    assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0));
    kern_pgdir[0] = 0;
    assert(pp0->pp_ref == 1);
    pp0->pp_ref = 0;

    // check pointer arithmetic in pgdir_walk
    page_free(pp0);
    va = (void *) (PGSIZE * NPDENTRIES + PGSIZE);
    ptep = pgdir_walk(kern_pgdir, va, 1);
    ptep1 = (pte_t *) KADDR(PTE_ADDR(kern_pgdir[PDX(va)]));
    assert(ptep == ptep1 + PTX(va));
    kern_pgdir[PDX(va)] = 0;
    pp0->pp_ref = 0;

    // check that new page tables get cleared
    memset(page2kva(pp0), 0xFF, PGSIZE);
    page_free(pp0);
    pgdir_walk(kern_pgdir, 0x0, 1);
    ptep = (pte_t *) page2kva(pp0);
    for (i = 0; i < NPTENTRIES; i++)
        assert((ptep[i] & PTE_P) == 0);
    kern_pgdir[0] = 0;
    pp0->pp_ref = 0;

    // give free list back
    page_free_list = fl;

    // free the pages we took
    page_free(pp0);
    page_free(pp1);
    page_free(pp2);


    // test mmio_map_region
    mm1 = (uintptr_t) mmio_map_region(0, 4097);
    mm2 = (uintptr_t) mmio_map_region(0, 4096);
    // check that they're in the right region
    assert(mm1 >= MMIOBASE && mm1 + 8192 < MMIOLIM);
    assert(mm2 >= MMIOBASE && mm2 + 8192 < MMIOLIM);
    // check that they're page-aligned
    assert(mm1 % PGSIZE == 0 && mm2 % PGSIZE == 0);
    // check that they don't overlap
    assert(mm1 + 8192 <= mm2);
    // check page mappings
    assert(check_va2pa(kern_pgdir, mm1) == 0);
    assert(check_va2pa(kern_pgdir, mm1 + PGSIZE) == PGSIZE);
    assert(check_va2pa(kern_pgdir, mm2) == 0);
    assert(check_va2pa(kern_pgdir, mm2 + PGSIZE) == ~0);
    // check permissions
    assert(*pgdir_walk(kern_pgdir, (void *) mm1, 0) & (PTE_W | PTE_PWT | PTE_PCD));
    assert(!(*pgdir_walk(kern_pgdir, (void *) mm1, 0) & PTE_U));
    // clear the mappings
    *pgdir_walk(kern_pgdir, (void *) mm1, 0) = 0;
    *pgdir_walk(kern_pgdir, (void *) mm1 + PGSIZE, 0) = 0;
    *pgdir_walk(kern_pgdir, (void *) mm2, 0) = 0;

    cprintf("check_page() succeeded!\n");
}

// check page_insert, page_remove, &c, with an installed kern_pgdir
static void
check_page_installed_pgdir(void) {
    struct PageInfo *pp, *pp0, *pp1, *pp2;
    struct PageInfo *fl;
    pte_t *ptep, *ptep1;
    uintptr_t va;
    int i;

    // check that we can read and write installed pages
    pp1 = pp2 = 0;
    assert((pp0 = page_alloc(0)));
    assert((pp1 = page_alloc(0)));
    assert((pp2 = page_alloc(0)));
    page_free(pp0);
    memset(page2kva(pp1), 1, PGSIZE);
    memset(page2kva(pp2), 2, PGSIZE);
    page_insert(kern_pgdir, pp1, (void *) PGSIZE, PTE_W);
    assert(pp1->pp_ref == 1);
    assert(*(uint32_t *) PGSIZE == 0x01010101U);
    page_insert(kern_pgdir, pp2, (void *) PGSIZE, PTE_W);
    assert(*(uint32_t *) PGSIZE == 0x02020202U);
    assert(pp2->pp_ref == 1);
    assert(pp1->pp_ref == 0);
    *(uint32_t *) PGSIZE = 0x03030303U;
    assert(*(uint32_t *) page2kva(pp2) == 0x03030303U);
    page_remove(kern_pgdir, (void *) PGSIZE);
    assert(pp2->pp_ref == 0);

    // forcibly take pp0 back
    assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0));
    kern_pgdir[0] = 0;
    assert(pp0->pp_ref == 1);
    pp0->pp_ref = 0;

    // free the pages we took
    page_free(pp0);

    cprintf("check_page_installed_pgdir() Succeeded!\n");
}
